﻿#include "edge_canny.h"

#include "blur_gaussian.h"
#include "conversion_grayscale.h"
#include "edge_sobel.h"

EdgeCanny::EdgeCanny(PNM* img) :
    Convolution(img)
{
    initMembers();
}

EdgeCanny::EdgeCanny(PNM* img, ImageViewer* iv) :
    Convolution(img, iv)
{
    initMembers();
}

void EdgeCanny::initMembers()
{
    m_orientation = math::matrix<double>(image->width(), image->height());
    m_magnitude   = math::matrix<double>(image->width(), image->height());

	for (int i = 0; i < 361; i++)
	{
		if((i >= 0 && i <= 22) || (i >= 158 && i <= 202) || (i >= 338 && i <= 360))
			bucket[i] = 0;
		else if((i >= 23 && i <= 67) || (i >= 203 && i <= 247))
			bucket[i] = 1;
		else if((i >= 68 && i <= 112) || (i >= 248 && i <= 292))
			bucket[i] = 2;
		else bucket[i] = 3;
	}
}

PNM* EdgeCanny::transform()
{
    int width  = image->width(),
        height = image->height();

    int upper_thresh = getParameter("upper_threshold").toInt(),
        lower_thresh = getParameter("lower_threshold").toInt();

    PNM* newImage = new PNM(width, height, QImage::Format_Indexed8);

	PNM* tempimage;
	auto grayscale = new ConversionGrayscale(image);
	tempimage = grayscale->transform();

	auto gaussianblur = new BlurGaussian(tempimage);
	tempimage = gaussianblur->transformForParameters(3, 1.6f);

	auto sobel = new EdgeSobel(tempimage);
	auto g_x = sobel->rawHorizontalDetection();
	auto g_y = sobel->rawVerticalDetection();

	for (int i = 0; i < width; i++)
	{
		for (int j = 0; j < height; j++)
		{
			m_magnitude(i, j) = sqrt(pow((*g_x)(i, j), 2) + pow((*g_y)(i, j), 2));

			auto v = atan((*g_y)(i, j)/(*g_x)(i, j));
			if(v < 0) v = 0;
			if(v > 360) v = 360;
			m_orientation(i, j) = v;
		}
	}

	std::list<QPoint> initialPoints;

	int theta;
	double m;
	int x, y, w, z;
	int bnum;
	auto edgecolor = QColor(255, 255, 255, 255);

	for (int i = 1; i < width - 1; i++)
	{
		for (int j = 1; j < height - 1; j++)
		{
			theta = m_orientation(i, j);
			m = m_magnitude(i, j);
			bnum = bucket[theta];
			
			switch (bnum)
			{
				case 0: //prawo i lewo
					x = -1; y = 0;
					w = 1; z = 0;
					break;
				case 1: //prawy gorny, lewy dolny
					x = -1; y = 1;
					w = 1; z = -1;
					break;
				case 2: //gora, dol
					x = 0; y = 1;
					w = 0; z = -1;
					break;
				case 3: 
					x = -1; y = -1;
					w = 1; z = 1;
					break;
			}

			auto neigh_1 = m_magnitude(i + x, j + y);
			auto neigh_2 = m_magnitude(i + w, j + z);
			
			if(m > neigh_1 && m > neigh_2 && m > upper_thresh)
			{
				newImage->setPixel(i, j, 0);
				initialPoints.push_back(QPoint(i, j));
			}
			else 
				newImage->setPixel(i, j, 255);
		}
	}

	while(initialPoints.size() != 0)
	{
		QPoint current = initialPoints.back();
		initialPoints.remove(current);

		int i = current.x();
		int j = current.y();
		theta = m_orientation(i, j);
		bnum = bucket[theta];

		switch (bnum)
		{
			case 0: //prawo i lewo
				x = -1; y = 0;
				w = 1; z = 0;
				break;
			case 1: //prawy gorny, lewy dolny
				x = -1; y = 1;
				w = 1; z = -1;
				break;
			case 2: //gora, dol
				x = 0; y = 1;
				w = 0; z = -1;
				break;
			case 3: 
				x = -1; y = -1;
				w = 1; z = 1;
				break;
		}

		float max_mag = -1.0f;

		//while w obu kierunkach równoczesnie zamiast for
		bool flag = true;
		int k = i + x;
		int l = j + y;

		while(flag)
		{
			if(k == width - 1|| l == height -1 || k == 0 || l == 0) { break; }

			if((qGray(newImage->pixel(i, j)) == 0) //dodany do krawedzi
			|| (m_magnitude(k, l) < lower_thresh) //moc mniejsza niz threshold
			|| (m_orientation(k, l) != theta)  //inny kierunek
			|| (isHighest(k, l, k - x, l - y, &m_magnitude, &m_orientation))) //jest najwiekszy sposrod sasiadow
			{
				flag = false; //przestaje dodawac
			}
			else
			{
				newImage->setPixel(k, l, 255); //dodaje do krawedzi i kontynuuje sprawdzanie
				k += x;
				l += y;

			}
		}

		flag = true;
		k = i + w;
		l = j + z;

		while(flag)
		{
			if(k == width - 1|| l == height -1 || k == 0 || l == 0) { break; }

			if((qGray(newImage->pixel(k, l)) == 0) //dodany do krawedzi
			|| (m_magnitude(k, l) < lower_thresh) //moc mniejsza niz threshold
			|| (m_orientation(k, l) != theta)  //inny kierunek
			|| (isHighest(k, l, k - w, l - z, &m_magnitude, &m_orientation))) //jest najwiekszy sposrod sasiadow
			{
				flag = false; //przestaje dodawac
			}
			else
			{
				newImage->setPixel(k, l, 255); //dodaje do krawedzi i kontynuuje sprawdzanie
				k += w;
				l += z;
			}
		}
	}

    return newImage;
}

bool EdgeCanny::isHighest(int x, int y, int p_x, int p_y, math::matrix<double>* magnitude, math::matrix<double>* orientation)
{
	//m_magnitude(k,l) ma największą wartość wśród swoich sąsiadów 
	//(z wyłaczeniem sasiada–piksela, z którego przyszedłeś) skierowanych w tym samym kierunku co (k,l)

	for(int i = -1; i <= 1; i++)
		for(int j = -1; j<=1; j++)
		{
			if(x + i != p_x && y + j != p_y) //nie jestem w rodzicu
				if((*orientation)(x + i, y + j) == (*orientation)(x, y)) //skierowany w tym samym kierunku
					if( (*magnitude)(x + i, y + j) >= (*magnitude)(x, y))  //moc wieksza lub rowna mojej
						return false; //nie jestem max
		}

	return true;
}

void EdgeCanny::checkHysterysis(int i, int j, int di, int dj, int direction, int lower_thresh, PNM* newImage)
{
    qDebug() << Q_FUNC_INFO << "Not implemented yet!";
}
 